package cn.edu.zut.score.service.impl;

import cn.edu.zut.score.core.cache.RedisCache;
import cn.edu.zut.score.core.constant.RoleEnum;
import cn.edu.zut.score.core.convert.GradeConvert;
import cn.edu.zut.score.core.domain.RestResponse;
import cn.edu.zut.score.core.dto.request.MarkRequest;
import cn.edu.zut.score.core.dto.response.GradeResponse;
import cn.edu.zut.score.core.entity.Grade;
import cn.edu.zut.score.core.entity.Reply;
import cn.edu.zut.score.core.entity.User;
import cn.edu.zut.score.mapper.GradeMapper;
import cn.edu.zut.score.service.IGradeService;
import cn.edu.zut.score.service.IReplyInfoService;
import cn.edu.zut.score.service.IReplyService;
import cn.edu.zut.score.service.IUserService;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.servlet.http.HttpServletRequest;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

/**
 * <p>
 * 服务实现类
 * </p>
 *
 * @author zby
 * @since 2021-06-25
 */
@Service
public class GradeServiceImpl extends ServiceImpl<GradeMapper, Grade> implements IGradeService {

    @Autowired
    private HttpServletRequest httpServletRequest;

    @Autowired
    private RedisCache redisCache;

    @Autowired(required = false)
    private GradeMapper gradeMapper;

    @Autowired
    private IReplyService iReplyService;

    @Autowired
    private IReplyInfoService iReplyInfoService;

    @Autowired
    private IUserService iUserService;

    @Override
    public RestResponse<List<GradeResponse>> getScoreById(Long replyid, Long studentid) {
        LambdaQueryWrapper<Grade> qw = new LambdaQueryWrapper<>();
        qw.eq(Grade::getReplyId, replyid);
        qw.eq(Grade::getStudentId, studentid);
        qw.orderByAsc(Grade::getId);
        List<Grade> grades = gradeMapper.selectList(qw);
        List<Grade> teas = grades.stream().filter(grade -> !grade.getMarkId().equals(0L)).collect(Collectors.toList());
        List<GradeResponse> res = GradeConvert.INSTANCE.grades2Responses(teas);
        List<Grade> stus = grades.stream().filter(grade -> grade.getMarkId().equals(0L)).collect(Collectors.toList());
        if (stus.size() != 0) {
            Integer average = (int) Math.round(stus.stream().mapToInt(Grade::getScore).average().orElse(0));
            GradeResponse gradeResponse = new GradeResponse();
            gradeResponse.setMarkId(0L);
            gradeResponse.setScore(average);
            res.add(gradeResponse);
        }
        return RestResponse.success(res);
    }

    @Override
    public RestResponse<List<GradeResponse>> getScoreByReplyId(Long replyid) {
        String token = httpServletRequest.getHeader("Authorization");
        User currentUser = redisCache.getCurrentUser(token);
        List<GradeResponse> result = getScoreById(replyid, currentUser.getId()).getResult();
        double ans = result.stream().filter(res -> !res.getMarkId().equals(0L)).mapToInt(GradeResponse::getScore).average().orElse(0);
        GradeResponse gradeResponse = result.stream().filter(res -> res.getMarkId().equals(0L)).findFirst().orElse(null);
        if (gradeResponse != null) {
            Integer weight = iReplyService.getById(replyid).getWeight();
            ans = (ans * (100 - weight) + gradeResponse.getScore() * weight) / 100;
        }
        GradeResponse response = new GradeResponse();
        response.setMarkId(666L);
        response.setScore((int) Math.round(ans));
        result.add(response);
        return RestResponse.success(result);
    }

    @Override
    public RestResponse<String> mark(MarkRequest markRequest) {
        String token = httpServletRequest.getHeader("Authorization");
        User currentUser = redisCache.getCurrentUser(token);

        LambdaQueryWrapper<Grade> qw = new LambdaQueryWrapper<>();
        qw.eq(Grade::getReplyId, markRequest.getReplyId());
        qw.eq(Grade::getCreateUser, currentUser.getId());
        if (gradeMapper.selectCount(qw) > 0) {
            return RestResponse.fail("您已评分，请勿重复评分");
        }

        Grade grade = new Grade();
        grade.setReplyId(markRequest.getReplyId());
        grade.setStudentId(markRequest.getStudentId());
        if (currentUser.getRole().equals(RoleEnum.student.getCode())) {
            grade.setMarkId(0L);
        } else {
            grade.setMarkId(currentUser.getId());
        }
        grade.setScore(markRequest.getScore());
        grade.fillCreateInfo(httpServletRequest, redisCache);
        this.saveOrUpdate(grade);
        return RestResponse.success("评分成功");
    }

    @Override
    public Integer getStudentSumScore(Long replyid, Long studentid, Integer weight) {
        LambdaQueryWrapper<Grade> qw = new LambdaQueryWrapper<>();
        qw.eq(Grade::getReplyId, replyid);
        qw.eq(Grade::getStudentId, studentid);
        List<Grade> grades = gradeMapper.selectList(qw);
        if (grades.size() == 0) {
            return 0;
        }
        double teaS = grades.stream().filter(grade -> !grade.getMarkId().equals(0L)).mapToInt(Grade::getScore).average().orElse(0);
        double stuS = grades.stream().filter(grade -> grade.getMarkId().equals(0L)).mapToInt(Grade::getScore).average().orElse(0);
        return (int) Math.round((teaS * (100 - weight) + stuS * weight) / 100);
    }

    @Override
    public RestResponse<List<Double>> analyseScale(Long replyId) {
        Reply reply = iReplyService.getById(replyId);
        if (reply == null) {
            return RestResponse.fail("答辩不存在");
        }
        if (reply.getVisible() == 0) {
            return RestResponse.fail("答辩未开启分析功能");
        }
        //依次为 0不及格、1及格、2中等、3良好、4优秀、5中位数、6众数、7平均分、8最高分、9最低分、10标准差
        Double[] res = {0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 100.0, 0.0};
        List<Long> ids = iReplyInfoService.getStudentIdsByReplyId(replyId);
        Double[] sumScore = new Double[ids.size()];
        int p = 0;

        Integer weight = reply.getWeight();

        LambdaQueryWrapper<Grade> qw = new LambdaQueryWrapper<>();
        qw.eq(Grade::getReplyId, replyId);
        List<Grade> grades = gradeMapper.selectList(qw);

        DecimalFormat df = new DecimalFormat("#.00");
        boolean minFlag = true;
        for (Long id : ids) {
            List<Grade> list = grades.stream().filter(grade -> grade.getStudentId().equals(id)).collect(Collectors.toList());
            if (list.size() == 0) {
                res[0]++;
            } else {
                minFlag = false;
                double teaS = grades.stream().filter(grade -> grade.getStudentId().equals(id) && !grade.getMarkId().equals(0L)).mapToInt(Grade::getScore).average().orElse(0);
                double stuS = grades.stream().filter(grade -> grade.getStudentId().equals(id) && grade.getMarkId().equals(0L)).mapToInt(Grade::getScore).average().orElse(0);
                double sum = Double.parseDouble(df.format((teaS * (100 - weight) + stuS * weight) / 100));
                sumScore[p++] = sum;
                if (sum >= 90) {
                    res[4]++;
                } else if (sum >= 80) {
                    res[3]++;
                } else if (sum >= 70) {
                    res[2]++;
                } else if (sum >= 60) {
                    res[1]++;
                } else {
                    res[0]++;
                }
                if (sum > res[8]) {
                    res[8] = sum;
                }
                if (sum < res[9]) {
                    res[9] = sum;
                }
            }
        }
        res[5] = Double.valueOf(df.format(median(sumScore, p)));
        res[6] = Double.valueOf(df.format(mode(sumScore, p)));
        res[7] = Double.valueOf(df.format(getAverage(sumScore, p)));
        if (minFlag) {
            res[9] = 0.0;
        }
        res[10] = Double.valueOf(df.format(standardDiviation(sumScore, p)));
        return RestResponse.success(Arrays.asList(res));
    }

    @Override
    public RestResponse<List<Double>> analyseStudent(Long replyId, Long studentId) {
        Reply reply = iReplyService.getById(replyId);
        if (reply == null) {
            return RestResponse.fail("答辩不存在");
        }
        if (iUserService.getById(studentId) == null) {
            return RestResponse.fail("学生不存在");
        }
        if (reply.getVisible() == 0) {
            return RestResponse.fail("答辩未开启分析功能");
        }
        //依次为 0不及格、1及格、2中等、3良好、4优秀、5中位数、6众数、7平均分、8最高分、9最低分、10标准差
        Double[] allRes = {0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 100.0, 0.0};
        //依次为 0差、1合格、2一般、3良好、4优秀（前五为教师）、5差、6合格、7一般、8良好、9优秀（后五为学生匿名） 全部为分段区间个数
        Double[] stuRes1 = {0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0};
        //依次为 0中位数、1众数、2总分、3最高分、4最低分、5标准差
        Double[] stuRes2 = {0.0, 0.0, 0.0, 0.0, 100.0, 0.0};

        List<Long> ids = iReplyInfoService.getStudentIdsByReplyId(replyId);
        Double[] sumScore = new Double[ids.size()];
        int p = 0;

        Integer weight = reply.getWeight();

        LambdaQueryWrapper<Grade> qw = new LambdaQueryWrapper<>();
        qw.eq(Grade::getReplyId, replyId);
        List<Grade> grades = gradeMapper.selectList(qw);

        List<Grade> stuGrades = new ArrayList<>();

        DecimalFormat df = new DecimalFormat("#.00");
        boolean minFlag = true;
        for (Long id : ids) {
            List<Grade> list = grades.stream().filter(grade -> grade.getStudentId().equals(id)).collect(Collectors.toList());
            if (id.equals(studentId)) {
                stuGrades = list;
            }
            if (list.size() == 0) {
                allRes[0]++;
            } else {
                minFlag = false;
                double teaS = grades.stream().filter(grade -> grade.getStudentId().equals(id) && !grade.getMarkId().equals(0L)).mapToInt(Grade::getScore).average().orElse(0);
                double stuS = grades.stream().filter(grade -> grade.getStudentId().equals(id) && grade.getMarkId().equals(0L)).mapToInt(Grade::getScore).average().orElse(0);
                double sum = Double.parseDouble(df.format((teaS * (100 - weight) + stuS * weight) / 100));
                sumScore[p++] = Double.valueOf(df.format(sum));
                if (id.equals(studentId)) {
                    stuRes2[2] = sum;
                }
                if (sum >= 90) {
                    allRes[4]++;
                } else if (sum >= 80) {
                    allRes[3]++;
                } else if (sum >= 70) {
                    allRes[2]++;
                } else if (sum >= 60) {
                    allRes[1]++;
                } else {
                    allRes[0]++;
                }
                if (sum > allRes[8]) {
                    allRes[8] = sum;
                }
                if (sum < allRes[9]) {
                    allRes[9] = sum;
                }
            }
        }
        allRes[5] = Double.valueOf(df.format(median(sumScore, p)));
        allRes[6] = Double.valueOf(df.format(mode(sumScore, p)));
        allRes[7] = Double.valueOf(df.format(getAverage(sumScore, p)));
        if (minFlag) {
            allRes[9] = 0.0;
        }
        allRes[10] = Double.valueOf(df.format(standardDiviation(sumScore, p)));

        stuRes1[0] = (double) stuGrades.stream().filter(grade -> !grade.getMarkId().equals(0L) && grade.getScore() >= 0 && grade.getScore() < 60).count();
        stuRes1[1] = (double) stuGrades.stream().filter(grade -> !grade.getMarkId().equals(0L) && grade.getScore() >= 60 && grade.getScore() < 69).count();
        stuRes1[2] = (double) stuGrades.stream().filter(grade -> !grade.getMarkId().equals(0L) && grade.getScore() >= 70 && grade.getScore() < 79).count();
        stuRes1[3] = (double) stuGrades.stream().filter(grade -> !grade.getMarkId().equals(0L) && grade.getScore() >= 80 && grade.getScore() < 89).count();
        stuRes1[4] = (double) stuGrades.stream().filter(grade -> !grade.getMarkId().equals(0L) && grade.getScore() >= 90 && grade.getScore() <= 100).count();
        stuRes1[5] = (double) stuGrades.stream().filter(grade -> grade.getMarkId().equals(0L) && grade.getScore() >= 0 && grade.getScore() < 60).count();
        stuRes1[6] = (double) stuGrades.stream().filter(grade -> grade.getMarkId().equals(0L) && grade.getScore() >= 60 && grade.getScore() < 69).count();
        stuRes1[7] = (double) stuGrades.stream().filter(grade -> grade.getMarkId().equals(0L) && grade.getScore() >= 70 && grade.getScore() < 79).count();
        stuRes1[8] = (double) stuGrades.stream().filter(grade -> grade.getMarkId().equals(0L) && grade.getScore() >= 80 && grade.getScore() < 89).count();
        stuRes1[9] = (double) stuGrades.stream().filter(grade -> grade.getMarkId().equals(0L) && grade.getScore() >= 90 && grade.getScore() <= 100).count();

        Double[] scores = stuGrades.stream().map(grade -> grade.getScore() * 1.0).toArray(Double[]::new);
        stuRes2[0] = Double.valueOf(df.format(median(scores, scores.length)));
        stuRes2[1] = Double.valueOf(df.format(mode(scores, scores.length)));
        stuRes2[3] = stuGrades.stream().mapToDouble(Grade::getScore).max().orElse(0);
        stuRes2[4] = stuGrades.stream().mapToDouble(Grade::getScore).min().orElse(0);
        stuRes2[5] = Double.valueOf(df.format(standardDiviation(scores, scores.length)));
        List<Double> res = new ArrayList<>();
        res.addAll(Arrays.asList(allRes));
        res.addAll(Arrays.asList(stuRes1));
        res.addAll(Arrays.asList(stuRes2));
        return RestResponse.success(res);
    }

    @Override
    public RestResponse<List<Double>> getAnalyse(Long replyId) {
        String token = httpServletRequest.getHeader("Authorization");
        User currentUser = redisCache.getCurrentUser(token);
        return analyseStudent(replyId, currentUser.getId());
    }

    //求中位数
    private double median(Double[] dArr, int n) {
        if (dArr.length <= 1) {
            return 0;
        }
        Arrays.sort(dArr, 0, n);
        if (n % 2 == 1) {
            return dArr[n / 2];
        } else {
            return (dArr[n / 2 - 1] + dArr[n / 2]) / 2;
        }
    }

    //求众数（无则返回0）
    private double mode(Double[] dArr, int length) {
        if (dArr.length <= 1) {
            return 0;
        }
        Arrays.sort(dArr, 0, length);
        Double res = dArr[0], temp = dArr[0];
        int n = 0, tn = 0;
        for (int i = 0; i < length; i++) {
            if (dArr[i].equals(res)) {
                n++;
            } else {
                if (dArr[i].equals(temp)) {
                    tn++;
                } else {
                    temp = dArr[i];
                    tn = 1;
                }
                if (tn >= n) {
                    res = temp;
                    n = tn;
                }
            }
        }
        return n == 1 ? 0.0 : res;
    }

    //求平均值
    private double getAverage(Double[] dArr, int n) {
        if (dArr.length <= 1) {
            return 0;
        }
        double sum = 0;
        for (int i = 0; i < n; i++) {
            sum += dArr[i];
        }
        return sum / n;
    }

    //求标准差
    private double standardDiviation(Double[] dArr, int n) {
        if (dArr.length <= 1) {
            return 0;
        }
        double dVar = 0;
        double dAve = getAverage(dArr, n);
        for (int i = 0; i < n; i++) {
            dVar += (dArr[i] - dAve) * (dArr[i] - dAve);
        }
        return Math.sqrt(dVar / n);
    }
}
